#!/usr/bin/env python2

import os
import sys
import time
import subprocess
import errno
import getopt
import re
import psutil

from buddha import lsb


from config import Config
from utils import _dmsg, _dwarn, _derror, _check_config, Exp, _check_crontab, \
            _get_network, _get_speed, _aton, _exec_system, _hostname2ip, _get_network_ipaddr, _exchange_ipmask


def _has(buf, str1):
    p = re.compile(str1, re.M)
    return p.search(buf)


class Env:
    def __init__(self, config, disk_manage=None):
        self.config = config
        self.disk_manage = disk_manage

        (distro, release, codename) = lsb.lsb_release()
        self.distro = distro
        self.codename = codename
        self.release = release

    def init(self):
        return 0

    def check_sysctl(self, fix=True):
        if self.config.coredump:
            _exec_system("ulimit -c unlimited", False)
        else:
            _exec_system("ulimit -c 0", False)

        if self.config.testing:
            return

        corepath = os.path.abspath(self.config.home + "/core/")
        os.system("mkdir -p %s" % (corepath))
        self.__check_sysctl("kernel.core_pattern", "%s/core-%%e-%%p-%%s" % (corepath), fix)
        _check_config("/etc/sysctl.conf", "kernel.core_pattern", "=", "%s/core-%%e-%%p-%%s" % (corepath), fix)

        #_dmsg("check sysctl")
        self.__check_sysctl("net.core.wmem_max", str(self.config.wmem_max), fix)
        self.__check_sysctl("net.core.rmem_max", str(self.config.rmem_max), fix)
        self.__check_sysctl("net.ipv4.ip_forward", "0", fix)

        _check_config("/etc/sysctl.conf", "net.core.wmem_max", "=", str(self.config.wmem_max), fix)
        _check_config("/etc/sysctl.conf", "net.core.rmem_max", "=", str(self.config.rmem_max), fix)
        _check_config("/etc/sysctl.conf", "net.ipv4.ip_forward", "=", "0", fix)
        #_dmsg("check sysctl finished")

    def check_systemd(self, fix=True):
        (distro, release, codename) = lsb.lsb_release()
        if (distro == 'CentOS'):
            version = int(release.split(".")[0])
            if version == 7:
                _exec_system("systemctl disable etcd 2>/dev/null", False)
                _exec_system("systemctl disable iscsid 2>/dev/null", False)

    def check_limits(self, fix=True):
        if self.config.testing:
            return

        #_dmsg("check limits")
        self.__check_limits(self.config.user, "hard nofile", str(self.config.nofile), fix)
        self.__check_limits(self.config.user, "soft nofile", str(self.config.nofile), fix)

    def check_iscsid(self, fix=True):
        #_dmsg("check iscsid")
        if (not os.path.exists("/etc/iscsi/iscsid.conf")):
            return

        if self.config.testing:
            return

        ctx = '''ACTION=="add", SUBSYSTEMS=="scsi", ATTRS{vendor}=="MDS", ATTRS{model}=="LICH-DISK", RUN+="/bin/sh -c 'echo %s > /sys$DEVPATH/device/timeout'"\n''' % self.config.iscsi_timeout
        fd = open('/etc/udev/rules.d/99-mds-iscsi.rules', 'w')
        fd.write(ctx)
        fd.close()
        _check_config("/etc/iscsi/iscsid.conf", "node.conn[0].timeo.login_timeout", "=", "15", fix)
        _check_config("/etc/iscsi/iscsid.conf", "node.conn[0].timeo.logout_timeout", "=", "15", fix)
        _check_config("/etc/iscsi/iscsid.conf", "node.conn[0].timeo.noop_out_interval", "=", "5", fix)
        _check_config("/etc/iscsi/iscsid.conf", "node.conn[0].timeo.noop_out_timeout", "=", "5", fix)
        _check_config("/etc/iscsi/iscsid.conf", "node.session.err_timeo.abort_timeout", "=", "15", fix)
        _check_config("/etc/iscsi/iscsid.conf", "node.session.err_timeo.lu_reset_timeout", "=", "30", fix)
        _check_config("/etc/iscsi/iscsid.conf", "node.session.err_timeo.tgt_reset_timeout", "=", "30", fix)
        #_check_config("/etc/iscsi/iscsid.conf", "node.session.timeo.replacement_timeout", "=", "300", fix)
        #_dmsg("check iscsid finished")

    def check_iptables(self, fix=True):
        if self.config.testing:
            return

        if (not os.path.exists("/etc/init.d/iptables")):
            cmd = "iptables -F"
            os.system(cmd)
            return

        cmd = "/etc/init.d/iptables stop"
        os.system(cmd)

    def set_swap(self):
        """
        vm.swappness = 0;
        vm.min_free_kbytes = 512 * 1024 * 1024;
        vm.vfs_cache_pressure = 5;
        vm.zone_reclaim_mode = 0;
        """
        if self.config.testing:
            return

        if (self.distro == 'Ubuntu'):
            os.system("echo 0 > /proc/sys/vm/swappiness")
            os.system("echo %d > /proc/sys/vm/min_free_kbytes" % (512 * 1024 * 1024))
            os.system("echo %d > /proc/sys/vm/vfs_cache_pressure" % (5))
            os.system("echo %d > /proc/sys/vm/zone_reclaim_mode" % (0))
            if (self.config.cgroup):
                os.system("echo %s > /proc/sys/vm/numa_zonelist_order" % ('zone'))
        elif (self.distro == 'CentOS'):
            os.system("echo 0 > /proc/sys/vm/swappiness")
            os.system("echo %d > /proc/sys/vm/min_free_kbytes" % (512 * 1024 * 1024))
            os.system("echo %d > /proc/sys/vm/vfs_cache_pressure" % (5))
            os.system("echo %d > /proc/sys/vm/zone_reclaim_mode" % (0))
            if (self.config.cgroup):
                os.system("echo %s > /proc/sys/vm/numa_zonelist_order" % ('zone'))
        else:
            pass

    def __restart_cron(self):
        if (self.distro == 'Ubuntu'):
            cmd = "/etc/init.d/cron restart > /dev/null"
            os.system(cmd)
        elif (self.distro == 'CentOS'):
            cmd = "/etc/init.d/crond restart > /dev/null"
            os.system(cmd)
        else:
            pass

    def __init_lichcron(self, lichcron):
        lichcron_tmp = lichcron + ".tmp"
        head = ["SHELL=/bin/bash\n",
                "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n",
                "# For details see man 4 crontabs\n\n",
                "# Example of job definition:\n",
                "# .---------------- minute (0 - 59,\n",
                "# |  .------------- hour (0 - 23,\n",
                "# |  |  .---------- day of month (1 - 31,\n",
                "# |  |  |  .------- month (1 - 12, OR jan,feb,mar,apr ...\n",
                "# |  |  |  |  .---- day of week (0 - 6, (Sunday=0 or 7, OR sun,mon,tue,wed,thu,fri,sat\n",
                "# |  |  |  |  |\n",
                "# *  *  *  *  * user-name command to be executed\n\n"]

        fd = open(lichcron_tmp, 'w')
        fd.writelines(head)
        fd.close()
        os.rename(lichcron_tmp, lichcron)

    def check_crontab(self):
        if os.path.exists("/etc/cron.d/lichcron"):
            cmd = "rm /etc/cron.d/lichcron"
            os.system(cmd)

        # delete lich tasks in /etc/crontab
        cmd = "sed -i '/%s/d' /etc/crontab" % (self.config.lich.replace('/', '\/'))
        os.system(cmd)

        self.__init_lichcron("/etc/cron.d/lichcron")

        log_path = os.path.abspath(self.config.home + '/log/')
        core_path = os.path.abspath(self.config.home + '/core/')

        if self.config.testing:
            _check_crontab("*/1 * * * *", "%s/bin/lich.node --metanode_balance" % (self.config.lich),  "%s/metanode_balance.log" % log_path)
            return

        if self.config.cgroup:
            _check_crontab("*/1 * * * *", "%s/cgroup/cgloop.sh " % (self.config.lich), "%s/cgroup.log" % log_path)

        #_check_crontab("*/10 * * * *", "%s/bin/lich.node --recover" % (self.config.lich), "%s/recover.log" % log_path)

        _check_crontab("*/30 * * * *", "%s/bin/lich.balance --balance 5" % (self.config.lich), "%s/volbalance.log" % log_path)
        _check_crontab("*/10 * * * *", "%s/bin/lich.node --metabalance" % (self.config.lich), "%s/metabalance.log" % log_path)
        #_check_crontab("1 */8 * * *", "%s/bin/lich.node --chunkbalance --rand" % (self.config.lich), "%s/chunkbalance.log" % log_path)
        _check_crontab("*/10 * * * *", "%s/bin/lich.node --metanode_balance" % (self.config.lich),  "%s/metanode_balance.log" % log_path)

        _check_crontab("0 1 * * *",    "%s/bin/lich.node --disk_check health" % (self.config.lich), "%s/disk_health.log" % log_path)
        _check_crontab("*/10 * * * *", "%s/bin/lich.node --disk_check cache" % (self.config.lich),  "%s/disk_cache.log" % log_path)
        _check_crontab("*/3 * * * *", "%s/bin/lich.node --disk_load -v" % (self.config.lich),  "%s/disk_load.log" % log_path)

        _check_crontab("*/10 * * * *", "%s/bin/lich.node --spare_check -v" % (self.config.lich), "%s/spare_check.log" % log_path)

        if self.config.iscsi_vip:
            _check_crontab("*/1 * * * *", "%s/bin/lich.node --vip_check" % (self.config.lich), "%s/vip_check.log" % log_path)

        if self.config.cleanlogcore:
            _check_crontab("0 */1 * * *", "%s/bin/lich.cleanlog %s" % (self.config.lich, log_path), "%s/cleanlog.log" % log_path)
            _check_crontab("0 */1 * * *", "%s/bin/lich.cleancore %s" % (self.config.lich, core_path), "%s/cleancore.log" % log_path)

        #self.__restart_cron()

    def unset_crontab(self):
        if self.config.testing:
            return

        if not os.path.exists("/etc/cron.d/lichcron"):
            cmd = "sed -i '/%s/d' /etc/crontab" % (self.config.lich.replace('/', '\/'))
            os.system(cmd)
            os.system("crontab /etc/crontab")
        else:
            cmd = "rm /etc/cron.d/lichcron"
            os.system(cmd)
            #self.__restart_cron()

    def set_cgroup_config(self):
        if self.config.testing:
            return

        cmd = "%s/cgroup/setconfig.pl > %s/setconfig.log 2>&1 > /dev/null" % (self.config.lich, os.path.abspath(self.config.home + "/log/"))
        #print(cmd)
        os.system(cmd)
        cmd = "%s/cgroup/setnic.sh > %s/setnic.log 2>&1 > /dev/null" % (self.config.lich, os.path.abspath(self.config.home + "/log/"))
        #print(cmd)
        os.system(cmd)

    def set_all(self, fix=True):
        os.system("echo 2 > /proc/sys/net/ipv4/conf/all/arp_ignore")
        #self.set_swap()
        #self.set_cgroup_config()
        self.check_iscsid(fix)
        self.disk_manage.disk_set('metawlog')

        if self.config.crontab:
            self.check_crontab()

        if self.config.nohosts:
            _dwarn("skip check_network with nohosts\n")
        else:
            try:
                #self.check_network()
                self.check_physical_resources()
            except Exp, e:
                _derror("%s\n" % str(e.err))
                exit(e.errno)

        try:
            self.check_sysctl(fix)
            self.check_systemd(fix)
            #self.check_limits(fix)
            self.check_iptables()
        except Exception, e:
            print (e)
            raise Exception(e)

    def __check_sysctl(self, key, value, fix):
        #cmd = "sysctl -a | grep '%s = %s' > /dev/null" % (key, value)
        cmd = "sysctl %s | grep '%s = %s' > /dev/null" % (key, key, value)
        #print (cmd)
        ret = subprocess.call(cmd, shell=True)
        if (ret):
            #_dwarn(cmd + " fail")
            if (fix):
                cmd = "sysctl -e %s=%s > /dev/null" % (key, value)
                ret = subprocess.call(cmd, shell=True)
                if (ret):
                    raise Exception("set key %s value %s fail" % (key, value))
                _dmsg(cmd)
            else:
                raise Exception("check key %s value %s fail" % (key, value))

    def __check_limits(self, user, key, value, fix):
        cmd = "grep -P '^%s[\t ]*%s[\t ]*%s$' /etc/security/limits.conf > /dev/null" % (user, key, value)
        ret = subprocess.call(cmd, shell=True)
        if (ret):
            _dwarn(cmd + " fail")
            if (fix):
                cmd = "grep -P '^%s[\t ]*%s' /etc/security/limits.conf > /dev/null" % (user, key)
                ret = subprocess.call(cmd, shell=True)
                if (ret):
                    cmd = "echo '%s        %s        %s' >> /etc/security/limits.conf" %(user, key, value)
                    ret = subprocess.call(cmd, shell=True)
                    if (ret):
                        raise Exception("sysctl set fail ret %u" % (ret))
                    _dmsg(cmd)
                else:
                    cmd = "sed -i 's/^%s[\t ]*%s.*$/%s        %s        %s/g' /etc/security/limits.conf > /dev/null" % (user, key, user, key, value)
                    ret = subprocess.call(cmd, shell=True)
                    if (ret):
                        raise Exception("sysctl set fail ret %u" % (ret))
                    _dmsg(cmd)
            else:
                raise Exception("sysctl check fail ret %u" % (ret))

    def check_network(self):
        '''
        check network only used for not nohosts mode
        '''
        if self.config.testing:
            return

        confignet = self.config.network()
        localnet = _get_network_ipaddr()

        #print (confignet)
        #print (localnet)

        found = False
        ip = _hostname2ip(self.config.hostname)
        if ip is None:
            raise Exp(errno.ENOENT, "network config error, please check hostname or /etc/hosts")

        for i in confignet:
            if (_aton(i[0]) & _aton(i[1])) == (_aton(ip) & _aton(i[1])):
                found = True

        if not found:
            raise Exp(errno.ENOENT, "network config error, please check lich.conf or /etc/hosts")

        net = []
        used = []
        for i in confignet:
            net.append(_aton(i[0]) & _aton(i[1]))
            used.append(0)

        vip = []
        if '/' in self.config.iscsi_vip:
            for vip_iscsi in self.config.iscsi_vip.split(','):
                vip.append(_exchange_ipmask(vip_iscsi))
        if '/' in self.config.ucarp_vip:
            vip.append(_exchange_ipmask(self.config.ucarp_vip))

        valid = False
        repeat = False
        eth = []
        for (k,v) in localnet.items():
            for x in v:
                if x in vip:
                    continue
                n = _aton(x[0]) & _aton(x[1])
                if n in net:
                    idx = net.index(n)
                    if used[idx] == 1:
                        repeat = True
                        break
                    used[idx] = 1

                    valid = True
                    if k not in eth:
                        eth.append((k, x))

        if repeat:
            raise Exp(errno.ENOENT, "network config error, please check `ifconfig' or `ip addr' repeat network")
        if not valid:
            raise Exp(errno.ENOENT, "network config error, please check lich.conf or `ifconfig'")

        count = 0
        for k in eth:
            if (k[0] == 'lo'):
                count = count + 1
                #_derror("lo is not recommended")
            else:
                try:
                    s = _get_speed(k[0])
                    if (int(s) >= 1000) or self.config.testing:
                        count = count + 1
                except Exp, e:
                    _derror(e.err)
                    exit(e.errno)
                print ("check %s:%s, speed %s" % (k[0], k[1], s))

        if (count == 0):
            raise Exp(errno.ENOENT, "network config error, please check speed of the interface")

    def check_cpu(self):
        core_count = psutil.cpu_count()
        if core_count < self.config.polling_core:
            _dwarn("The number of polling core[%s] is more than the number of physical core[%s]. \
                    \nPlease modify %s/etc/lich.conf: polling_core" % \
                    (self.config.polling_core, core_count, self.config.home))
            #exit(errno.EPERM)

    def check_memory(self):
        phymem = psutil.virtual_memory().total/1024/1024
        lichmem = self.config.memcache_count * (self.config.memcache_seg/1024/1024) * (self.config.polling_core+1)
        if phymem < lichmem:
            _dwarn("The physical memory[%sM] does not meet the process requirements[%sM]. \
                    \nPlease modify %s/etc/lich.conf: memcache_count" % \
                    (phymem, lichmem, self.config.home))
            #exit(errno.EPERM)

    def check_physical_resources(self):
        self.check_cpu()
        self.check_memory()
        if not self.config.nohosts:
            self.check_network()


def usage():
    print ("usage:")
    print (sys.argv[0] + " --help")
    print (sys.argv[0] + " --set")
    print (sys.argv[0] + " --init")
    print (sys.argv[0] + " --check")
    print (sys.argv[0] + " --unsetcrontab")


def main():
    try:
        opts, args = getopt.getopt(
            sys.argv[1:], 
            'h', ['set', 'init', 'help', 'unsetcrontab', 'check']
        )
    except getopt.GetoptError, err:
        print str(err)
        usage()
        return

    config = Config()
    env = Env(config)
    for o, a in opts:
        if o in ('--help'):
            usage()
            exit(0)
        elif o == '--init':
            env.init()
        elif o == '--set':
            env.set_all()
        elif o == '--unsetcrontab':
            env.unset_crontab()
        elif o == '--check':
            env.check_physical_resources()
        else:
            assert False, 'oops, unhandled option: %s, -h for help' % o
            exit(1)


if __name__ == '__main__':
    if len(sys.argv) == 1:
        usage()
    else:
        main()
